package com.sikong.geotools.geometryCRS;

import org.geotools.data.*;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.JTS;
import org.geotools.map.FeatureLayer;
import org.geotools.map.MapContent;
import org.geotools.referencing.CRS;
import org.geotools.styling.SLD;
import org.geotools.styling.Style;
import org.geotools.swing.JMapFrame;
import org.geotools.swing.action.SafeAction;
import org.geotools.swing.data.JFileDataStoreChooser;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureVisitor;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.util.ProgressListener;
import org.locationtech.jts.geom.Geometry;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;

/**
 * 名词解释 CRS ---> coordinate reference system
 *这是一段用于通过窗口化界面进行要素投影的代码，主要有：
 * 1、通过窗口化界面选择shp文件进行展示
 * 2、通过设定其他坐标系进行投影变换，即时展示要素形状
 * 3、添加export功能，将投影后的要素进行导出
 * 具体运行时选择resources中任意一个shp文件(wgs84，序号4326)，通过设定序号为3005的坐标系观察具体效果
 *
 * @see <a href="https://docs.geotools.org/latest/userguide/tutorial/geometry/geometrycrs.html">Geometry CRS Tutorial</a>
 *
 * @author 梁超 / stan
 * @since  0.1.0
 * @Date   2022/03/12 1:07 AM
 */
public class CRSLab {

    /**
     * :sourceFile         shp文件的存储路径
     * :featureSource      shp文件的featureSource
     * :map                图形化界面
     */
    private File sourceFile;
    private SimpleFeatureSource featureSource;
    private MapContent map;

    /**
     * 创建一个内部类，添加导出shp文件功能的按钮
     */
    class ExportShapefileAction extends SafeAction {
        ExportShapefileAction() {
            super("Export...");
            putValue(Action.SHORT_DESCRIPTION,"Export using current crs");
        }

        public void action(ActionEvent e) throws FactoryException, IOException {
            exportToShapefile();
        }

        /**
         * 具体实现shp文件导出按钮的功能
         */
        private void exportToShapefile() throws FactoryException, IOException {
            SimpleFeatureType schema = featureSource.getSchema();
            JFileDataStoreChooser chooser = new JFileDataStoreChooser("shp");
            chooser.setDialogTitle("Save reprojected shapefile");
            chooser.setSaveFile(sourceFile);
            int returnVal = chooser.showSaveDialog(null);
            if (returnVal != JFileDataStoreChooser.APPROVE_OPTION) {
                return;
            }

            File file = chooser.getSelectedFile();
            if (file.equals(sourceFile)) {
                JOptionPane.showMessageDialog(null,"cannot replace " + file);
                return;
            }

            /**
             * 创建坐标系变换之间的方法
             */
            CoordinateReferenceSystem dataCRS = schema.getCoordinateReferenceSystem();
            CoordinateReferenceSystem worldCRS = map.getCoordinateReferenceSystem();
            boolean lenient = true;
            MathTransform transform = CRS.findMathTransform(dataCRS, worldCRS, lenient);

            SimpleFeatureCollection featureCollection = featureSource.getFeatures();

            ShapefileDataStoreFactory factory = new ShapefileDataStoreFactory();
            HashMap<String, Serializable> create = new HashMap<>();
            create.put("url",file.toURI().toURL());
            create.put("create spatial index",Boolean.TRUE);
            DataStore dataStore = factory.createNewDataStore(create);
            SimpleFeatureType featureType = SimpleFeatureTypeBuilder.retype(schema, worldCRS);
            dataStore.createSchema(featureType);

            String createdName = dataStore.getTypeNames()[0];

            /**
             * 通过构建好的投影变化方法transform，将原有坐标系变化后写入至要导出shp
             */
            Transaction transaction = new DefaultTransaction("Reproject");
            try (FeatureWriter<SimpleFeatureType,SimpleFeature> writer = dataStore.getFeatureWriterAppend(createdName,transaction);
                SimpleFeatureIterator iterator = featureCollection.features()) {
                while (iterator.hasNext()) {
                    SimpleFeature feature = iterator.next();
                    SimpleFeature copy = writer.next();
                    copy.setAttributes(feature.getAttributes());

                    Geometry geometry = (Geometry) feature.getDefaultGeometry();
                    Geometry geometry2 = JTS.transform(geometry, transform);

                    copy.setDefaultGeometry(geometry2);
                    writer.write();
                }
                transaction.commit();
                JOptionPane.showMessageDialog(null,"Export to shapefile complete");
            } catch (Exception problem) {
                problem.printStackTrace();
                transaction.rollback();
                JOptionPane.showMessageDialog(null,"Export to shapefile failed");
            } finally {
                transaction.close();
            }
        }
    }

    /**
     * 创建一个内部类，添加验证shp文件坐标精度的按钮
     */
    class ValidateGeometryAction extends SafeAction{
        ValidateGeometryAction() {
            super("Validate geometry");
            putValue(Action.SHORT_DESCRIPTION,"Check each geometry");
        }

        /**
         * 具体实现精度验证功能
         */
        public int validateFeatureGeometry(ProgressListener progress) throws IOException {
            final SimpleFeatureCollection featureCollection = featureSource.getFeatures();

            class ValidationVisitor implements FeatureVisitor {
                public int numInvalidGeometries = 0;

                public void visit(Feature f) {
                    SimpleFeature feature = (SimpleFeature) f;
                    Geometry geom = (Geometry) feature.getDefaultGeometry();
                    if (geom != null && !geom.isValid()) {
                        numInvalidGeometries++;
                        System.out.println("Invalid Geometry: " + feature.getID());
                    }
                }
            }
            ValidationVisitor visitor = new ValidationVisitor();

            featureCollection.accepts(visitor,progress);
            return visitor.numInvalidGeometries;
        }



        public void action(ActionEvent e) throws IOException {
            int numInvalid = validateFeatureGeometry(null);
            String msg;
            if (numInvalid == 0) {
                msg = "All feature geometries are valid";
            } else {
                msg = "Invalid geometries: " + numInvalid;
            }
            JOptionPane.showMessageDialog(null,msg,"Geometry results",JOptionPane.INFORMATION_MESSAGE);
        }
    }

    /**
     * 通过JFileDataStoreChooser构建窗口化界面选择shp文件
     * 添加相应的按钮功能
     */
    public void displayShapefile() throws IOException {
        this.sourceFile = JFileDataStoreChooser.showOpenFile("shp",new File("src/main/resources"),null);
        if (this.sourceFile == null) {
            return;
        }

        FileDataStore dataStore = FileDataStoreFinder.getDataStore(this.sourceFile);
        this.featureSource = dataStore.getFeatureSource();

        this.map = new MapContent();
        Style style = SLD.createSimpleStyle(this.featureSource.getSchema());
        FeatureLayer layer = new FeatureLayer(this.featureSource, style);
        this.map.layers().add(layer);

        JMapFrame mapFrame = new JMapFrame(this.map);
        mapFrame.enableToolBar(true);
        mapFrame.enableStatusBar(true);

        JToolBar toolBar = mapFrame.getToolBar();
        toolBar.addSeparator();
        toolBar.add(new JButton(new ValidateGeometryAction()));
        toolBar.add(new JButton(new ExportShapefileAction()));

        mapFrame.setSize(800,600);
        mapFrame.setVisible(true);

    }

}
